The next thread-centric topic you will examine in this chapter is the role of the CLR thread pool. When you invoke a method asynchronously using delegate types (via the BeginInvoke() method), the CLR does not literally create a brand-new thread. For purposes of efficiency, a delegate’s BeginInvoke() method leverages a pool of worker threads that is maintained by the runtime. To allow you to interact with this pool of waiting threads, the System.Threading namespace provides the ThreadPool class type.
If you wish to queue a method call for processing by a worker thread in the pool, you can make use of the ThreadPool.QueueUserWorkItem() method. This method has been overloaded to allow you to specify an optional System.Object for custom state data in addition to an instance of the WaitCallback delegate:
public static class ThreadPool { ... public static bool QueueUserWorkItem(WaitCallback callBack); public static bool QueueUserWorkItem(WaitCallback callBack, object state); }
The WaitCallback delegate can point to any method that takes a System.Object as its sole parameter (which represents the optional state data) and returns nothing. Do note that if you do not provide a System.Object when calling QueueUserWorkItem(), the CLR automatically passes a null value. To illustrate queuing methods for use by the CLR thread pool, ponder the following program, which makes use of the Printer type once again. In this case, however, you are not manually creating an array of Thread objects; rather, you are assigning members of the pool to the PrintNumbers() method:
class Program { static void Main(string[] args) { Console.WriteLine("***** Fun with the CLR Thread Pool *****\n"); Console.WriteLine("Main thread started. ThreadID = {0}", Thread.CurrentThread.ManagedThreadId); Printer p = new Printer(); WaitCallback workItem = new WaitCallback(PrintTheNumbers); // Queue the method ten times. for (int i = 0; i < 10; i++) { ThreadPool.QueueUserWorkItem(workItem, p); } Console.WriteLine("All tasks queued"); Console.ReadLine(); } static void PrintTheNumbers(object state) { Printer task = (Printer)state; task.PrintNumbers(); } }
At this point, you may be wondering if it would be advantageous to make use of the CLR-maintained thread pool rather than explicitly creating Thread objects. Consider these benefits of leveraging the thread pool:
However, using manual thread management is preferred in some cases, for example:
If you require a thread with a fixed identity in order to abort it, suspend it, or discover it by name.
That wraps up your investigation of the System.Threading namespace. While you can use this information to build some responsive applications, the remainder of this chapter will examine a new addition to the base class libraries brought forth with .NET 4.0- The Task Parallel Library.